{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First we'll generate a random matrix"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "#Number of columns (features)\n",
    "K = 5\n",
    "\n",
    "#Number of records\n",
    "N = 1000\n",
    "\n",
    "#Generate an NxK matrix of uniform random variables\n",
    "X = np.random.random([N, K])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's peak at our data to confirm it looks as we expect it"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.72449726, 0.44997821, 0.8366405 , 0.00932524, 0.97046662])"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X[100, :]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1000, 5)"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "X.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This exercise is about designing a scoring function for a logistic regression. As we are not concerned with fitting a model to data, we can just make up a logistic regression. <br> <br>\n",
    "\n",
    "For quick intro, the Logistic Regression takes the form of $\\hat{Y} = f(x * \\beta^T)$, where $x$ is the $1xK$ vector of features and $\\beta$ is the $1xK$ vector of weights. The function $f$, called a 'link' function, is the inverse logit: <br><br>\n",
    "\n",
    "<center>$f(a)=\\frac{1}{1+e^{-a}}$</center> <br><br>\n",
    "\n",
    "In this notebook we'll write a function that, given inputs of $X$ and $\\beta$, returns a value for $\\hat{Y}$.\n",
    "<br><br>\n",
    "First let's generate a random set of weights to represent $\\beta$.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([-0.64353499, -0.04367242, -0.65085735,  0.07703089, -0.95397106])"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "beta = 2 * (np.random.random(K) - 0.5)\n",
    "beta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice how we applied a neat NumPy trick here. The numpy.random.random() function returns an array, yet we applied what appears to be a scalar operation on the vector. This is an example of what NumPy calls vectorization and <a href=\"http://docs.scipy.org/doc/numpy/user/basics.broadcasting.html\">broadcasting</a>, which offers us both a very fast way to do run vector computations as well as a clean and concise method of coding. \n",
    "\n",
    "\n",
    "\n",
    "<b>Question: we designed the above $beta$ vector such that $E[\\beta_i]=0$. How can we confirm that we did this correctly?</b>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "-0.4430009851648524"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "#start by taking the mean of the beta we already calculated\n",
    "beta.mean()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "#It is likely the above is not equal to zero. Let's simulate this 100k times and see what the average mean is\n",
    "\n",
    "sims = 100000\n",
    "means = []\n",
    "for i in range(sims):\n",
    "    means.append(2 * (np.random.random(K) - 0.5).mean())\n",
    "    "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's use matplotlibs hist function to plot the histogram of means here. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYcAAAD8CAYAAACcjGjIAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMi4zLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvIxREBQAAEr9JREFUeJzt3X+s3fV93/Hnq3ah3bIMEzyPAYmd\n1lXldpqTWoCWSc2Pyhgi1VRFmZFa3JTWUQNTq3VSnOYPIlI0MqmNhJbS0cbFbF0IJY3whjPXIVRV\npZpw01LAMOIbQoQ9B7uYJK2ikULf++N8bvKNP/f6Xt9f5zI/H9LR/Z739/P9ft/n66P7ut8f5zhV\nhSRJQ9837gYkSSuP4SBJ6hgOkqSO4SBJ6hgOkqSO4SBJ6hgOkqSO4SBJ6hgOkqTO6nE3MF8XXXRR\nrV+/ftxtSNJryhe/+MW/qaq1s417zYbD+vXrmZiYGHcbkvSakuSrcxnnaSVJUsdwkCR1DAdJUsdw\nkCR1DAdJUsdwkCR1Zg2HJJcleTjJU0kOJ/nVVv9wkmNJHmuPawbLfDDJZJJnklw1qG9rtckkuwf1\nDUkeafVPJTlvsV+oJGnu5nLk8Arw61W1CbgSuCnJpjbvY1W1uT32A7R5O4AfA7YBv5NkVZJVwMeB\nq4FNwPWD9Xy0reuHgZeAGxfp9UmS5mHWcKiq41X1l236b4GngUvOsMh24N6qermqvgJMApe3x2RV\nPVtV3wbuBbYnCfBO4P62/F7g2vm+IEnSwp3VJ6STrAfeAjwCvA24OckNwASjo4uXGAXHocFiR/lu\nmDx/Wv0K4A3A16vqlWnGS68563c/OLZtP3f7u8e2bf3/Zc4XpJO8Dvg08GtV9U3gTuCHgM3AceC3\nlqTD7+1hV5KJJBMnT55c6s1J0jlrTuGQ5PsZBcMfVtUfA1TVC1X1alX9A/B7jE4bARwDLhssfmmr\nzVR/EbggyerT6p2ququqtlTVlrVrZ/3eKEnSPM3lbqUAnwCerqrfHtQvHgz7GeDJNr0P2JHk/CQb\ngI3AF4BHgY3tzqTzGF203ldVBTwMXNeW3wk8sLCXJUlaiLlcc3gb8PPAE0kea7XfYHS30WaggOeA\n9wFU1eEk9wFPMbrT6aaqehUgyc3AAWAVsKeqDrf1fQC4N8lvAn/FKIwkSWMyazhU1Z8DmWbW/jMs\ncxtw2zT1/dMtV1XP8t3TUpKkMXvN/n8O0mzGedeQ9Frn12dIkjqGgySpYzhIkjqGgySpYzhIkjqG\ngySpYzhIkjqGgySpYzhIkjqGgySpYzhIkjqGgySpYzhIkjqGgySpYzhIkjqGgySpYzhIkjqGgySp\nYzhIkjqGgySpYzhIkjqGgySpYzhIkjqGgySpYzhIkjqGgySpYzhIkjqGgySpYzhIkjqGgySpYzhI\nkjqGgySpM2s4JLksycNJnkpyOMmvtvqFSQ4mOdJ+rmn1JLkjyWSSx5O8dbCunW38kSQ7B/WfSPJE\nW+aOJFmKFytJmpu5HDm8Avx6VW0CrgRuSrIJ2A08VFUbgYfac4CrgY3tsQu4E0ZhAtwCXAFcDtwy\nFShtzC8Pltu28JcmSZqvWcOhqo5X1V+26b8FngYuAbYDe9uwvcC1bXo7cE+NHAIuSHIxcBVwsKpO\nVdVLwEFgW5v3+qo6VFUF3DNYlyRpDM7qmkOS9cBbgEeAdVV1vM36GrCuTV8CPD9Y7Girnal+dJr6\ndNvflWQiycTJkyfPpnVJ0lmYczgkeR3waeDXquqbw3ntL/5a5N46VXVXVW2pqi1r165d6s1J0jlr\nTuGQ5PsZBcMfVtUft/IL7ZQQ7eeJVj8GXDZY/NJWO1P90mnqkqQxmcvdSgE+ATxdVb89mLUPmLrj\naCfwwKB+Q7tr6UrgG+300wFga5I17UL0VuBAm/fNJFe2bd0wWJckaQxWz2HM24CfB55I8lir/QZw\nO3BfkhuBrwLvafP2A9cAk8C3gPcCVNWpJB8BHm3jbq2qU236/cDdwA8Cn20PSdKYzBoOVfXnwEyf\nO3jXNOMLuGmGde0B9kxTnwB+fLZeJEnLw09IS5I6hoMkqWM4SJI6hoMkqWM4SJI6hoMkqWM4SJI6\nhoMkqWM4SJI6hoMkqWM4SJI6hoMkqWM4SJI6hoMkqWM4SJI6hoMkqWM4SJI6hoMkqWM4SJI6hoMk\nqbN63A1IWjzrdz84lu0+d/u7x7JdLR2PHCRJHcNBktQxHCRJHcNBktQxHCRJHcNBktQxHCRJHT/n\noCU1rvvuJS2MRw6SpI7hIEnqGA6SpM6s4ZBkT5ITSZ4c1D6c5FiSx9rjmsG8DyaZTPJMkqsG9W2t\nNplk96C+Ickjrf6pJOct5guUJJ29uRw53A1sm6b+sara3B77AZJsAnYAP9aW+Z0kq5KsAj4OXA1s\n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\nQmCC\n",
      "text/plain": [
       "<Figure size 600x400 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "plt.hist(means)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We should expect the distribution to be centered around zero. Is it? As fun technical side, let's dive a little deeper into what this distribution should look like. The histogram shows a distribution of the average of a sample of 5 uniformly distributed random variables taken over N different samples. Can we compare this to a theoretical distribution?<br>\n",
    "\n",
    "Yes we can! We sampled each $\\beta_i$ from a uniform distribution over the interval $[-1, 1]$. The variance of a sample of uniformly distributed variables is given by $(1/12) * (b - a)^2$, where $b$ and $a$ are the min/max of the support interval. The standard error (or the standard deviation of the mean) of a sample of size K with with $Var(X) = \\sigma^2$ is $\\sigma / \\sqrt(K)$. <br>\n",
    "\n",
    "Given the above knowledge, we should expect our distribution of averages to be normally distributed with mean = 0 and var = $(12 * 5)^{-1} * (1 - (-1))^2 = 0.66667$. Let's compare this normal distribution to our sample above."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 600x400 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#Compute a vector from the normal distribution specified above\n",
    "from scipy.stats import norm\n",
    "mu = 0\n",
    "sig = np.sqrt(4 / 60.0) \n",
    "xs = np.linspace(-1, 1, 1000)\n",
    "ys = norm.pdf(xs, mu, sig) \n",
    "\n",
    "plt.hist(means, density = True)\n",
    "plt.plot(xs, ys)\n",
    "plt.show()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's write our scoring function. Let's try to use as much of Numpy's inner optimization as possible (hint, this can be done in two lines and without writing any loops). The key is that numpy functions that would normally take in a scalar can also take in an array, and the function applies the operations element wise to the array and returns an array. i.e.:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([1, 1])"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ex_array = np.array([-1, 1])\n",
    "np.abs(ex_array)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's use this feature to write a fast and clean scoring function"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def score_logistic_regression(X, beta):\n",
    "    '''\n",
    "    This function takes in an NxK matrix X and 1xK vector beta.\n",
    "    The function should apply the logistic scoring function to each record of X.\n",
    "    The output should be an Nx1 vector of scores\n",
    "    '''\n",
    "    \n",
    "    #First let's calculate X*beta - make sure to use numpy's 'dot' method\n",
    "    xbeta = X.dot(beta)\n",
    "    \n",
    "    #Now let's input this into the link function\n",
    "    prob_score = 1 / (1 + np.exp(-1 * xbeta))\n",
    "    \n",
    "    return prob_score"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "So how much faster is it by using Numpy? We can test this be writing the same function that uses no Numpy and executes via loops."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def score_logistic_regression_NoNumpy(X, beta):\n",
    "    '''\n",
    "    This function takes in an NxK matrix X and 1xK vector beta.\n",
    "    The function should apply the logistic scoring function to each record of X.\n",
    "    The output should be an Nx1 vector of scores\n",
    "    '''\n",
    "    #Let's calculate xbeta using loops\n",
    "    xbeta = []\n",
    "    for row in X:\n",
    "        \n",
    "        xb = 0\n",
    "        for i, el in enumerate(row):\n",
    "            xb += el * beta[i]\n",
    "        \n",
    "        xbeta.append(xb)\n",
    "        \n",
    "    #Now let's apply the link function to each xbeta\n",
    "    prob_score = []\n",
    "    for xb in xbeta:\n",
    "        prob_score.append(1 / (1 + np.exp(-1 * xb)))\n",
    "        \n",
    "    return prob_score"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Before doing any analysis, let's test the output of each to make sure they equal"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Mean Absolute Deviation = 0.0\n"
     ]
    }
   ],
   "source": [
    "diff = np.abs(score_logistic_regression_NoNumpy(X, beta) - score_logistic_regression(X, beta))\n",
    "\n",
    "print('Mean Absolute Deviation = {}'.format(np.round(diff.sum(), 8)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If they equal then we can proceed with timing analysis"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "100 loops, best of 3: 5.41 ms per loop\n"
     ]
    }
   ],
   "source": [
    "%timeit score_logistic_regression_NoNumpy(X, beta)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The slowest run took 33.37 times longer than the fastest. This could mean that an intermediate result is being cached.\n",
      "10000 loops, best of 3: 22.3 µs per loop\n"
     ]
    }
   ],
   "source": [
    "%timeit score_logistic_regression(X, beta)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
